package com.example.tool.otherui;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONObject;
import com.example.tool.entity.ChartFun;
import com.example.tool.es.Es7Client;
import com.jfoenix.controls.*;
import io.datafx.controller.ViewController;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.chart.*;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.util.Pair;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

@ViewController(value = "/fxml/ui/chartViewLog.fxml", title = "内容展示panel")
public class ChartViewQueryLogController {
    @FXML
    private JFXRadioButton platform_all;
    @FXML
    private JFXRadioButton platform_meituan;

    @FXML
    private JFXRadioButton platform_ele;
    @FXML
    private JFXTimePicker startTimePicker;

    @FXML
    private JFXTimePicker endTimePicker;

    @FXML
    private JFXDatePicker runDatePicker;

    @FXML
    private JFXComboBox<Label> taskTypeComboBox;


    @FXML
    private JFXComboBox<Label> finishComboBox;


    @FXML
    private JFXComboBox<Label> statusComboBox;


    @FXML
    private JFXComboBox<String> sourceComboBox;

    //
    @FXML
    private JFXComboBox<String> serviceComboBox;

    @FXML
    private JFXButton searchTable;
    @FXML
    private JFXTextField keywordText;
    @FXML
    private JFXComboBox<Label> keywordComboBox;

    @FXML
    private VBox tableViewId;

    @FXML
    private JFXComboBox<Label> chartComboBox;

    private ThreadLocal<Date> startTimeThreadLocal = new ThreadLocal<>();

    private static final Map<String, Integer> taskTypeMap = new HashMap<>();
    private static final Map<String, Integer> finishMap = new HashMap<>();
    private static final Map<String, Integer> statusMap = new HashMap<>();
    private static final Map<String, String> keywordMap = new HashMap<>();
    private static final Comparator<Terms.Bucket> bucketComparator = Comparator.comparingLong((Terms.Bucket o) -> o.getDocCount()).reversed();


    private static final Function<JFXComboBox<String>, String> comboBoxValFun = stringJFXComboBox -> {
        String value = stringJFXComboBox.getValue();
        if (StringUtils.equals(value, "--请选择--")) {
            return null;
        }
        if (StringUtils.isNotEmpty(value)) {
            value = value.split("\\(")[0];
        }
        return value;
    };

    private static final Function<LocalDate, Date> localDateDateFunction = localDate -> {
        String value = localDate.toString();
        try {
            return new SimpleDateFormat("yyyy-MM-dd").parse(value);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    };
    private static final Function<Pair<Date, LocalTime>, Date> localTimeFunction = pair -> {
        Date date = pair.getKey();
        String time = pair.getValue().toString();
        String timeStr = new SimpleDateFormat("yyyy-MM-dd").format(date) + " " + time;
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(timeStr);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    };

    static {
        taskTypeMap.put("历史推广", 1);
        taskTypeMap.put("实时推广", 2);
        taskTypeMap.put("站外支付", 5);
        taskTypeMap.put("站外授权",6);
        taskTypeMap.put("菜品清洗",8);
        finishMap.put("--请选择--", null);
        finishMap.put("完成", 1);
        finishMap.put("执行中", 0);
        statusMap.put("--请选择--", null);
        statusMap.put("成功", 1);
        statusMap.put("失败", 0);

        keywordMap.put("账号", "accountId");
        keywordMap.put("店铺id", "shopIds");
        keywordMap.put("任务id", "id");
        keywordMap.put("父任务id", "parentTaskId");
    }

    @PostConstruct
    public void init() throws IOException {
        final ToggleGroup toggleGroup = new ToggleGroup();
        platform_meituan.setToggleGroup(toggleGroup);
        platform_all.setToggleGroup(toggleGroup);
        platform_ele.setToggleGroup(toggleGroup);
        platform_all.setSelected(true);

        startTimePicker.set24HourView(true);
        endTimePicker.set24HourView(true);
        startTimePicker.setConverter(new LocalTimeStringConverter2());
        endTimePicker.setConverter(new LocalTimeStringConverter2());
        startTimePicker.setValue(LocalTime.of(0, 0));
        endTimePicker.setValue(LocalTime.of(23, 59));
        runDatePicker.setValue(LocalDate.now());
        keywordComboBox.setValue(keywordComboBox.getItems().get(0));
        chartComboBox.setValue(chartComboBox.getItems().get(0));
        Consumer<Pair<ParsedStringTerms, JFXComboBox<String>>> initComboBoxFun = tuple3 -> {
            JFXComboBox<String> comboBox = tuple3.getValue();
            ObservableList<String> children = comboBox.getItems();
            children.clear();
            children.add("--请选择--");
            ParsedStringTerms sourceGroup = tuple3.getKey();
            for (Terms.Bucket bucket : sourceGroup.getBuckets().stream()
                    .sorted(bucketComparator).collect(Collectors.toList())) {
                String source = bucket.getKeyAsString();
                long docCount = bucket.getDocCount();
                children.add(source + "(" + docCount + ")");
            }
        };

        taskTypeComboBox.setOnAction(event -> {
            Label taskTypeValue = taskTypeComboBox.getValue();
            if(taskTypeValue!=null){
                Date date = localDateDateFunction.apply(runDatePicker.getValue());
                JSONObject headers = Es7Client.statisticsHeader(taskTypeMap.get(taskTypeValue.getText()), date
                        , "dkd_chain_monitor_task_record_" + DateUtil.format(date, "YYYY_MM"));
                initComboBoxFun.accept(new Pair<>((ParsedStringTerms) headers.get("source"), sourceComboBox));
                initComboBoxFun.accept(new Pair<>((ParsedStringTerms) headers.get("serviceName"), serviceComboBox));
            }
        });
        statusComboBox.setValue(statusComboBox.getItems().get(0));
        finishComboBox.setValue(finishComboBox.getItems().get(0));
        //taskTypeComboBox.setValue(taskTypeComboBox.getItems().get(0));
        searchTable.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                boolean all = platform_all.isSelected();
                boolean ele = platform_ele.isSelected();
                Integer platform = all ? null : (ele ? 2 : 1);
                String taskTypeValue = taskTypeComboBox.getValue().getText();
                Integer taskType = taskTypeMap.get(taskTypeValue);
                Integer finish = finishMap.get(finishComboBox.getValue().getText());
                Integer status = statusMap.get(statusComboBox.getValue().getText());
                String source = comboBoxValFun.apply(sourceComboBox);
                String serviceName = comboBoxValFun.apply(serviceComboBox);
                Date runDate = localDateDateFunction.apply(runDatePicker.getValue());
                Date startTime = localTimeFunction.apply(new Pair<>(runDate, startTimePicker.getValue()));
                Date endTime = localTimeFunction.apply(new Pair<>(runDate, endTimePicker.getValue()));
                String keywordType = keywordComboBox.getValue().getText();
                String keyword = keywordText.getText();
                SearchSourceBuilder searchBuilder = new SearchSourceBuilder();
                BoolQueryBuilder bool = QueryBuilders.boolQuery();
                term("platform", platform, bool);
                term("taskType", taskType, bool);
                term("finish", finish == null ? null : (finish == 1 ? true : false), bool);
                term("status", status, bool);
                term("source", source, bool);
                term("serviceName", serviceName, bool);
                term(keywordMap.get(keywordType), keyword, bool);
                term("runDate", Integer.valueOf(new SimpleDateFormat("yyyyMMdd").format(runDate)), bool);

                bool.filter(QueryBuilders.rangeQuery("startTime")
                        .gte(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime))
                        .lte(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTime)));
                searchBuilder.query(bool);
                try {
                    JSONObject data = Es7Client.statisticsTask(searchBuilder, "dkd_chain_monitor_task_record_" + DateUtil.format(new Date(), "YYYY_MM"));

                    tableViewId.getChildren().clear();
                    String chartType = chartComboBox.getValue().getText();
                    switch (chartType) {
                        case "账号统计图":
                            tableViewId.getChildren().add(barChartFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "xAxis", "账号id",
                                            "yAxis", "任务数",
                                            "name", "accountId",
                                            "x->", selfMapper,
                                            "y->", selfMapper
                                    ), data
                            )));
                            break;
                        case "进度统计图":
                            pieFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "title", "进度统计图",
                                            "name", "finish",
                                            "x->", (Function<String, String>) x -> {
                                                if (BooleanUtils.isTrue(Boolean.valueOf(x))) {
                                                    return "完成";
                                                } else {
                                                    return "执行中";
                                                }
                                            },
                                            "y->", selfMapper
                                    ), data
                            )).buildChar(tableViewId);
                            break;
                        case "状态统计图":
                            pieFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "title", "状态统计图",
                                            "name", "status",
                                            "x->", (Function<String, String>) x -> {
                                                if ("1".equals(x)) {
                                                    return "成功";
                                                } else {
                                                    return "失败";
                                                }
                                            },
                                            "y->", selfMapper
                                    ), data
                            )).buildChar(tableViewId);
                            break;
                        case "来源统计图":
                            pieFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "title", "来源统计图",
                                            "name", "source",
                                            "x->", selfMapper,
                                            "y->", selfMapper
                                    ), data
                            )).buildChar(tableViewId);
                            break;
                        case "服务统计图":
                            pieFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "title", "服务统计图",
                                            "name", "serviceName",
                                            "x->", selfMapper,
                                            "y->", selfMapper
                                    ), data
                            )).buildChar(tableViewId);
                            break;
                        case "耗时统计图":
                            pieFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "title", "服务统计图",
                                            "name", "cost",
                                            "x->", (Function<String, String>) str -> {
                                                String[] arr = str.split("-");
                                                Integer start = Double.valueOf(arr[0]).intValue();
                                                Integer end = Double.valueOf(arr[1]).intValue();
                                                return minFun.apply(new Pair<>(start, end));

                                            },
                                            "y->", selfMapper
                                    ), data
                            )).buildChar(tableViewId);
                            break;
                        case "时间分布统计图":
                            tableViewId.getChildren().add(barChartFunction.apply(new Pair<>(
                                    buildJsonObject(
                                            "xAxis", "小时",
                                            "yAxis", "任务数",
                                            "name", "startTime",
                                            "x->", (Function<String, String>) x -> x.split(" ")[1].substring(0, 2),
                                            "y->", selfMapper
                                    ), data
                            )));
                            break;

                    }


                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    private Function<Pair<Integer, Integer>, String> minFun = p -> {
        double min1 = p.getKey() / 1000d / 60d;
        double min2 = p.getValue() / 1000d / 60d;
        if (p.getValue().equals(Integer.MIN_VALUE) || min2>100) {
            return min1 + "以上";
        }
        return trimZero(String.valueOf(min1)) + "~" + trimZero(String.valueOf(min2));
    };

    private String trimZero(String numStr) {

        String[] arr = numStr.split("\\.");
        if (arr[1].startsWith("0")) {
            if (arr[1].matches("0+")) {
                return arr[0];
            }
        }
        return numStr;

    }

    public final Function<Pair<JSONObject, JSONObject>, ChartFun.PieChartFun> pieFunction = pair -> {
        Object data = pair.getValue().get(pair.getKey().getString("name"));
        Map<String, Double> dataMap = new LinkedHashMap<>();
        Function<String, String> xfun = (Function<String, String>) pair.getKey().get("x->");
        Function<Long, Long> yfun = (Function<Long, Long>) pair.getKey().get("y->");
        if (data instanceof ParsedStringTerms) {
            ParsedStringTerms list = (ParsedStringTerms) data;
            List<? extends Terms.Bucket> buckets = list.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                String x = bucket.getKeyAsString();
                long y = bucket.getDocCount();
                dataMap.put(xfun.apply(x), Double.valueOf(yfun.apply(y)));
            }
        } else if (data instanceof ParsedLongTerms) {
            ParsedLongTerms list = (ParsedLongTerms) data;
            List<? extends Terms.Bucket> buckets = list.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                String x = bucket.getKeyAsString();
                long y = bucket.getDocCount();
                dataMap.put(xfun.apply(x), Double.valueOf(yfun.apply(y)));
            }
        } else if (data instanceof ParsedRange) {
            ParsedRange list = (ParsedRange) data;
            List<? extends Range.Bucket> buckets = list.getBuckets();
            for (Range.Bucket bucket : buckets) {
                String x = bucket.getKeyAsString();
                long y = bucket.getDocCount();
                dataMap.put(xfun.apply(x), Double.valueOf(yfun.apply(y)));
            }
        } else {
            try {
                throw new Exception("not supproted:" + data.getClass().getCanonicalName());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }


        ChartFun.PieChartFun fun = new ChartFun.PieChartFun(800, 800, pair.getKey().getString("title"), dataMap);
        return fun;
    };


    private Function<Object, Object> selfMapper = t -> t;
    public final Function<Pair<JSONObject, JSONObject>, List<Node>> lineChartFun = pair -> {
        final CategoryAxis xAxis = new CategoryAxis();//定义一个x轴(数字横轴)
        final NumberAxis yAxis = new NumberAxis();//定义一个y轴(数字纵轴)
        xAxis.setLabel(pair.getKey().getString("xAxis"));
        yAxis.setLabel(pair.getKey().getString("yAxis"));
        LineChart<String, Number> barChart = new LineChart<>(xAxis, yAxis);
        javafx.scene.control.Label label = new javafx.scene.control.Label();
        label.setId("showLabelId" + System.currentTimeMillis());
        label.setStyle("-fx-font-size: 15;");
        label.setWrapText(true);
        label.setTextFill(Color.BLUE);
        label.setPrefWidth(300);
        Object data = pair.getValue().get(pair.getKey().get("name"));
        if (data instanceof ParsedStringTerms) {
            ParsedStringTerms list = (ParsedStringTerms) data;
            List<? extends Terms.Bucket> buckets = list.getBuckets();
            addSeries(barChart, buckets, pair.getKey(), label);
        } else if (data instanceof ParsedLongTerms) {
            ParsedLongTerms list = (ParsedLongTerms) data;
            List<? extends Terms.Bucket> buckets = list.getBuckets();
            addSeries(barChart, buckets, pair.getKey(), label);
        } else {
            try {
                throw new Exception("not supproted:" + data.getClass().getCanonicalName());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        barChart.setPrefHeight(1000);
        barChart.setPrefWidth(1800);
        return Arrays.asList(barChart, label);
    };

    private void addSeries(LineChart<String, Number> barChart, List<? extends Terms.Bucket> buckets, JSONObject settings, Label label) {
        XYChart.Series<String, Number> qpsSeries = new XYChart.Series<>();
        //qpsSeries.setName(ycode);
        for (Terms.Bucket bucket : buckets) {
            long y = bucket.getDocCount();
            String x = bucket.getKeyAsString();
            JSONObject extra = new JSONObject();
            Function<String, String> xfun = (Function<String, String>) settings.get("x->");
            Function<Number, Number> yfun = (Function<Number, Number>) settings.get("y->");
            extra.put("x", xfun.apply(x));
            extra.put("y", yfun.apply(y));
            qpsSeries.getData().add(new XYChart.Data<>(xfun.apply(x), yfun.apply(y), extra));
        }
        barChart.getData().add(qpsSeries);
        for (XYChart.Data<String, Number> datum : qpsSeries.getData()) {
            JSONObject extraValue = (JSONObject) datum.getExtraValue();
            datum.getNode().setOnMousePressed(event -> {
                label.setTranslateX(event.getSceneX() - barChart.getWidth());
                label.setTranslateY(event.getSceneY());
                label.setText("x:" + extraValue.get("x") + "\ny:" + extraValue.get("y"));
            });

        }
    }

    private final Function<Pair<JSONObject, JSONObject>, BarChart> barChartFunction = pair -> {
        final CategoryAxis xAxis = new CategoryAxis();//定义一个x轴(数字横轴)
        final NumberAxis yAxis = new NumberAxis();//定义一个y轴(数字纵轴)
        xAxis.setLabel(pair.getKey().getString("xAxis"));
        yAxis.setLabel(pair.getKey().getString("yAxis"));
        Object accountIdMap = pair.getValue().get(pair.getKey().get("name"));
        BarChart<String, Number> barChart = new BarChart<>(xAxis, yAxis);
        XYChart.Series<String, Number> series = new XYChart.Series<>();
        Function<String, String> xfun = (Function<String, String>) pair.getKey().get("x->");
        Function<Number, Number> yfun = (Function<Number, Number>) pair.getKey().get("y->");
        if (accountIdMap instanceof ParsedLongTerms) {
            ParsedLongTerms d = (ParsedLongTerms) accountIdMap;
            for (Terms.Bucket bucket : d.getBuckets()) {
                String accountId = bucket.getKeyAsString();
                long taskNum = bucket.getDocCount();
                series.getData().add(new XYChart.Data<>(xfun.apply(accountId), yfun.apply(taskNum)));
            }
        } else if (accountIdMap instanceof ParsedStringTerms) {
            ParsedStringTerms d = (ParsedStringTerms) accountIdMap;
            for (Terms.Bucket bucket : d.getBuckets()) {
                String accountId = bucket.getKeyAsString();
                long taskNum = bucket.getDocCount();
                series.getData().add(new XYChart.Data<>(xfun.apply(accountId), yfun.apply(taskNum)));
            }
        } else if (accountIdMap instanceof ParsedDateHistogram) {
            ParsedDateHistogram d = (ParsedDateHistogram) accountIdMap;
            for (Histogram.Bucket bucket : d.getBuckets()) {
                String accountId = bucket.getKeyAsString();
                long taskNum = bucket.getDocCount();
                series.getData().add(new XYChart.Data<>(xfun.apply(accountId), yfun.apply(taskNum)));
            }
        }

        barChart.getData().add(series);
        barChart.setPrefHeight(800);
        barChart.setStyle("-fx-font-size: 20px");

        return barChart;
    };

    private JSONObject buildJsonObject(Object... ks) {
        int size = ks.length / 2;
        JSONObject settings = new JSONObject();
        for (int i = 0; i < size; i++) {
            Object key = ks[i * 2];
            Object value = ks[i * 2 + 1];
            settings.put(key.toString(), value);
        }
        return settings;
    }

    public javafx.scene.control.Label buildPaddingLabel(int width, int height) {
        javafx.scene.control.Label label = new javafx.scene.control.Label();
        label.setPrefSize(width, height);
        return label;
    }

    private void term(String key, Object value, BoolQueryBuilder bool) {
        if (value == null) {
            return;
        }
        if (value instanceof String && StringUtils.isEmpty(value.toString())) {
            return;
        }
        bool.filter(QueryBuilders.termQuery(key, value));
    }

    public static ObservableList<Node> getChildren(Class<?> clazz, Object instance) {
        Method getChildren = null;
        try {
            Class<?> a = clazz;
            while (true) {
                try {
                    getChildren = a.getDeclaredMethod("getChildren");
                    if (getChildren != null) {
                        break;
                    }
                } catch (Exception e) {
                    a = a.getSuperclass();
                }
            }


            getChildren.setAccessible(true);
            Object result = getChildren.invoke(instance);
            return (ObservableList<Node>) result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
